package web.suzy.oj.config;

import com.alibaba.druid.pool.DruidDataSource;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.context.config.annotation.RefreshScope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.Optional;

/**
 * YangSuzy 软件工作室
 * 类名: DruidConfig
 * 描述: 数据源配置类
 * 功能: ---- 待定 -----
 *
 * @author YangSuzy
 * Date: 2022/11/8 18:36
 */

@Configuration
@Slf4j(topic = "suzyoj")
@RefreshScope
@Data
public class DruidConfig {
    @Value("${mysql-username}")
    private String username;

    @Value("${mysql-password}")
    private String password;

    @Value("${mysql-host}")
    private String host;

    @Value("${mysql-port}")
    private Integer port;

    @Value("${mysql-name}")
    private String name;

    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;

    @Value("${spring.datasource.type}")
    private String type;

    /**
     * 初始化时建立物理连接的个数。
     * 初始化发生在显示调用init方法，或者第一次getConnection时
     */
    @Value("${spring.datasource.initial-size:10}")
    private Integer initialSize;

    /**
     * 开启PSCache
     */
    @Value("${spring.datasource.poolPreparedStatements:true}")
    private Boolean poolPreparedStatements;

    /**
     * 设置PSCache值
     */
    @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize:20}")
    private Integer maxPoolPreparedStatementPerConnectionSize;

    /**
     * 关闭空闲连接的检测时间间隔.
     * Destroy线程会检测连接的间隔时间，如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。
     */
    @Value("${spring.datasource.timeBetweenEvictionRunsMillis:60000}")
    private Integer timeBetweenEvictionRunsMillis;

    /**
     * 连接的最小生存时间.连接保持空闲而不被驱逐的最小时间
     */
    @Value("${spring.datasource.minEvictableIdleTimeMillis:300000}")
    private Integer minEvictableIdleTimeMillis;

    /**
     * 验证数据库服务可用性的sql.
     * 用来检测连接是否有效的sql 因数据库方言而差, 例如 MySql 应该写成 SELECT 1
     */
    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;

    /**
     * 申请连接时检测空闲时间，根据空闲时间再检测连接是否有效.
     * 建议配置为 true，不影响性能，并且保证安全性。
     * 申请连接的时候检测，如果空闲时间大于timeBetweenEvictionRun
     */
    @Value("${spring.datasource.testWhileIdle:true}")
    private Boolean testWhileIdle;

    /**
     * 申请连接时直接检测连接是否有效.
     * 申请连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。
     */
    @Value("${spring.datasource.testOnBorrow:false}")
    private Boolean testOnBorrow;

    /**
     * 归还连接时检测连接是否有效.
     * 归还连接时执行validationQuery检测连接是否有效，做了这个配置会降低性能。
     */
    @Value("${spring.datasource.testOnReturn:false}")
    private Boolean testOnReturn;

    /**
     * 连接出错后再尝试连接三次
     */
    @Value("${spring.datasource.connectionErrorRetryAttempts:3}")
    private Integer connectionErrorRetryAttempts;

    /**
     * 数据库服务宕机自动重连机制
     */
    @Value("${spring.datasource.breakAfterAcquireFailure:true}")
    private Boolean breakAfterAcquireFailure;

    /**
     * 连接出错后重试时间间隔
     */
    @Value("${spring.datasource.timeBetweenConnectErrorMillis:300000}")
    private Integer timeBetweenConnectErrorMillis;

    /**
     * 最小连接池数量
     */
    @Value("${spring.datasource.min-idle:20}")
    private Integer minIdle;

    /**
     * 最大连接池数量
     */
    @Value("${spring.datasource.maxActive:200}")
    private Integer maxActive;

    /**
     * 获取连接时最大等待时间，单位毫秒。
     * 配置了maxWait之后，缺省启用公平锁，并发效率会有所下降，如果需要可以通过配置
     */
    @Value("${spring.datasource.maxWait:60000}")
    private Integer maxWait;

    /**
     * 数据库属性配置
     */
    @Autowired
    private DataSourceConfig dataSourceConfig;

    @Bean(name = "datasource")
    @RefreshScope
    public DruidDataSource dataSource() {

        //获取数据库属性，如果存在评测机数据库属性则使用评测机数据库属性
        String mysqlHost = Optional.ofNullable(dataSourceConfig.getHost()).orElseGet(() -> host);
        Integer mysqlPort = Optional.ofNullable(dataSourceConfig.getPort()).orElseGet(() -> port);
        String mysqlName = Optional.ofNullable(dataSourceConfig.getName()).orElseGet(() -> name);
        String mysqlUsername = Optional.ofNullable(dataSourceConfig.getUsername()).orElseGet(() -> username);
        String mysqlUserPassword = Optional.ofNullable(dataSourceConfig.getPassword()).orElseGet(() -> password);

        log.warn("[MySQL] [Config Init] name:[{}], host:[{}], port:[{}], username:[{}], password:[{}]",
                mysqlName, mysqlHost, mysqlPort, mysqlUsername, mysqlUserPassword);

        DruidDataSource datasource = new DruidDataSource();
        String url = "jdbc:mysql://" + mysqlHost + ":" + mysqlPort + "/" + mysqlName + "?useUnicode=true&characterEncoding=utf-8&serverTimezone=Asia/Shanghai&allowMultiQueries=true&rewriteBatchedStatements=true";
        datasource.setUrl(url);
        datasource.setUsername(mysqlUsername);
        datasource.setPassword(mysqlUserPassword);
        datasource.setDriverClassName(driverClassName);
        datasource.setDbType(type);
        datasource.setMaxActive(maxActive);
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxWait(maxWait);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setConnectionErrorRetryAttempts(connectionErrorRetryAttempts);
        datasource.setBreakAfterAcquireFailure(breakAfterAcquireFailure);
        datasource.setTimeBetweenConnectErrorMillis(timeBetweenConnectErrorMillis);
        return datasource;
    }
}
